/*
 * Copyright 2008 Les Hazlewood
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pojodm.party;

import org.pojodm.security.Role;

import java.text.DateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class User extends Person {

    /**
     * Requires 6 or more alphanumeric and/or punctuation characters.
     */
    public static final Pattern VALID_PASSWORD_PATTERN = Pattern.compile( "[\\p{Alnum}\\p{Punct}]{6,255}" );

    public static final Pattern VALID_USERNAME_PATTERN = Pattern.compile( "[\\p{Alnum}_-]{1,255}" );

    public static final String ROOT_USER_USERNAME = "root";

    private String username;
    private String password;
    private String passwordResetKey = null;
    private Date passwordResetKeyTimestamp = null;
    private Date lastLoginTimestamp; //can be null if never logged in
    private Date lockTimestamp; //date the account was locked, null means unlocked (default behavior)
    private boolean sessionTimeoutEnabled = true; //per-user session configuration

    private Set<Role> roles;

    //Not a persistent property - used for form submissions:
    private String passwordConfirm = null;

    public User() {
    }


    /**
     * Returns the username associated with this user account;
     *
     * @return the username associated with this user account;
     */
    public String getUsername() {
        return username;
    }

    public void setUsername( String username ) {
        this.username = username;
    }

    /**
     * Returns the password for this user.
     *
     * @return this user's password
     */
    public String getPassword() {
        return password;
    }

    public void setPassword( String password ) {
        this.password = password;
    }

    /**
     * If the user forgets their password, this key is set first.  If they then request to reset their password, they
     * must submit this key for the reset request to be valid.  Otherwise, the password reset is timed out.
     * @return
     */
    public String getPasswordResetKey() {
        return passwordResetKey;
    }

    public void setPasswordResetKey( String passwordResetKey ) {
        this.passwordResetKey = passwordResetKey;
    }

    public Date getPasswordResetKeyTimestamp() {
        return passwordResetKeyTimestamp;
    }

    public void setPasswordResetKeyTimestamp( Date passwordResetKeyTimestamp ) {
        this.passwordResetKeyTimestamp = passwordResetKeyTimestamp;
    }

    /**
     * Returns the timestamp this User last logged in successfully to the application, or
     * <tt>null</tt> if the user has never logged in.
     *
     * @return the timestamp this User last logged in successfully to the application, or
     *         <tt>null</tt> if the user has never logged in.
     */
    public Date getLastLoginTimestamp() {
        return lastLoginTimestamp;
    }

    /**
     * Sets the timestamp this User last logged in successfully to the application.
     *
     * @param lastLoginTimestamp the timestamp this User last logged in successfully to the
     *                           application.
     */
    public void setLastLoginTimestamp( Date lastLoginTimestamp ) {
        this.lastLoginTimestamp = lastLoginTimestamp;
    }

    /**
     * Returns the time when this account was locked, either due to too many login attempts, an
     * explicit lock-out by an administrator, or because of some other security reason. <p>This
     * method returns <tt>null</tt> if the account is not locked and is considered to be in good
     * standing</p>
     *
     * @return the time when this account was locked, or <tt>null</tt> if this account is not locked
     *         and is considered to be in good standing.
     */
    public Date getLockTimestamp() {
        return lockTimestamp;
    }

    public void setLockTimestamp( Date lockTimestamp ) {
        this.lockTimestamp = lockTimestamp;
    }

    /**
     * Returns whether or not this particular user account can expire due to inactivity. <p>Defaults
     * to <tt>true</tt> as almost all user accounts should expire due to inactivity.
     *
     * @return <tt>true</tt> if this user's sessions can timeout due to inactivity, <tt>false</tt>
     *         otherwise.
     */
    public boolean isSessionTimeoutEnabled() {
        return sessionTimeoutEnabled;
    }

    public void setSessionTimeoutEnabled( boolean sessionTimeoutEnabled ) {
        this.sessionTimeoutEnabled = sessionTimeoutEnabled;
    }

    /**
     * Returns whether or not this user account is locked, thereby preventing further log-ins.
     *
     * @return <tt>true</tt> if this user account is locked and will not be allowed to log-in,
     *         <tt>false</tt>
     */
    public boolean isLocked() {
        return getLockTimestamp() != null;
    }

    /**
     * Convenience method for updating the state to locked.
     *
     * @see #getLockTimestamp()
     * @param locked whether or not this user account will be locked.
     */
    public void setLocked( boolean locked ) {
        if ( locked ) {
            if ( getLockTimestamp() == null ) {
                setLockTimestamp( new Date() );
            }
        } else {
            setLockTimestamp( null );
        }
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles( Set<Role> roles ) {
        this.roles = roles;
    }

    public String getPasswordConfirm() {
        return passwordConfirm;
    }

    public void setPasswordConfirm( String passwordConfirm ) {
        this.passwordConfirm = passwordConfirm;
    }

    /**
     * Adds a Role to this user's collection of {@link #getRoles() roles}.
     * <p/>
     * <p>If the existing roles collection is <tt>null</tt>, a new collection will be created and
     * assigned to this User and then the Role will be added.
     *
     * @param r the Role to add/associate with this User
     */
    public void addRole( Role r ) {
        Set<Role> roles = getRoles();
        if ( roles == null ) {
            roles = new HashSet<Role>();
            setRoles( roles );
        }
        roles.add( r );
    }

    public boolean removeRole( Role r ) {
        Set<Role> roles = getRoles();
        return roles != null && roles.remove(r);
    }

    public boolean hasRole( String roleName ) {
        Set<Role> roles = getRoles();

        if ( roles != null ) {
            for ( Role role : roles ) {
                if ( role.getName().equals( roleName ) ) {
                    return true;
                }
            }
        }
        return false;
    }

    public static boolean isValidPassword( String password ) {
        return password != null && VALID_PASSWORD_PATTERN.matcher( password ).matches();
    }

    public static boolean isValidUsername( String username ) {
        return username != null && VALID_USERNAME_PATTERN.matcher( username ).matches();
    }

    public StringBuffer toStringBuffer() {
        StringBuffer sb = super.toStringBuffer();
        sb.append( ",username=" ).append( getUsername() );
        sb.append( ",password=<protected>" );
        DateFormat df = DateFormat.getInstance();
        Date ts = getLastLoginTimestamp();
        if ( ts != null ) {
            sb.append( ",lastLoginTimestamp=" ).append( df.format( ts ) );
        }
        ts = getLockTimestamp();
        if ( ts != null ) {
            sb.append( ",lockTimestamp=" ).append( df.format( ts ) );
        }
        sb.append( ",sessionTimeoutEnabled=" ).append( isSessionTimeoutEnabled() );

        return sb;
    }

    public boolean onEquals( Object o ) {
        if ( o instanceof User ) {
            User u = (User) o;
            return getUsername().equals( u.getUsername() );
        }

        return false;
    }

    public int hashCode() {
        return getUsername().hashCode();
    }

    @Override
    @SuppressWarnings( {"CloneDoesntDeclareCloneNotSupportedException"} )
    public Object clone() {
        User clone = (User) super.clone();
        clone.setUsername( getUsername() );
        clone.setPassword( getPassword() );
        clone.setPasswordResetKey( getPasswordResetKey() );
        clone.setPasswordResetKeyTimestamp( getPasswordResetKeyTimestamp() );
        clone.setLastLoginTimestamp( getLastLoginTimestamp() );
        clone.setLockTimestamp( getLockTimestamp() );
        clone.setSessionTimeoutEnabled( isSessionTimeoutEnabled() );
        return clone;
    }

    public static void main( String[] args ) {
        String username = "s-ls";
        if ( !isValidUsername( username ) ) {
            System.out.println( "Not a valid username!" );
        } else {
            System.out.println( "Valid username." );
        }
    }

    public void accept(PartyVisitor visitor) {
        visitor.visit( this );
    }
}

